"""
chord.net.net_requests

Provides the Chord server-server communication API.

Every function here
  - has parameters (destination_server, *args)
  - returns None on failure (maybe more specific information later)
  - with name <func> sends a request which should be handled by
    chord.server_api.<func>_handler
    
Note: For security purposes, server-side exceptions should never be thrown directly back to the client.
      Instead, only transmit back relevant information, like ie the exception message.

"""

# When ready, you can replace the above line with the below line
import jackson.extjson.ext_json as json
import urllib2, urllib

from jackson import debug


def get_predecessor(dest):
    """ Asks dest for its predecessor.
    @author: rk
    @rtype: a chord.Server object
    """
    context = [__name__, 'get_predecessor()']
    debug.enter(context, None)
    f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/get_predecessor")
    assert f.info().gettype() == 'application/json'
    response = json.loads(f.read()) # this response is now a Server object
    debug.comment(context, "json object with hash is: " + str(response))
    debug.leave(context, None)
    return response

def get_successor(dest):
    """ Asks dest for its successor.
    @author: rk
    @rtype: a chord.Server object
    @change: 12-31-08 uses ext_json
    """
    context = [__name__, 'get_successor()']
    debug.enter(context, { 'existing_server (get_successor argument)':dest })
    f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/get_successor")
    assert f.info().gettype() == 'application/json'
    response = json.loads(f.read()) # this response is now a Server object
    debug.leave(context, None)
    return response

def is_predecessor(dest, key):
    """ Asks dest whether it is the predecessor of key.
    @author: rk
    @rtype: True or False
    """
    try:
        json_data = json.dumps(key)
        f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/is_predecessor",
                            urllib.urlencode({'data':json_data}))
        assert f.info().gettype() == 'application/json'
        response = json.loads(f.read()) # should be true or false. json does the bool conversion
    except Exception, e:
        print "chord.net.is_predecessor(): problem: ",e
        return None
    return response

def closest_preceding_finger(dest, key):
    """ Asks dest for its closest preceding finger
    @author: rk
    @rtype: a Server
    
    """
    try:
        #print "Extracting data"
        json_data = json.dumps(key)
        #print "Performing urlopen"
        f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/closest_preceding_finger",
                            urllib.urlencode({'data':json_data}))
        #print "Asserting json response type"
        #print "f.read():\n", f.read()
        assert f.info().gettype() == 'application/json'
        #print "Making response"
        response = json.loads(f.read()) # this is a Server instance
        #print "Returning response"
        return response
    except Exception, e:
        print "chord.net.closest_preceding_finger(", dest, key, "): problem:", e
        raise e

def join(dest, server):
    """ Submits a join request to dest on behalf of given server.
    @author: rk
    @rtype: { "successor" : a chord.Server object, "predecessor" : a chord.Server object }
    """
    context = [__name__, 'join()']
    debug.enter(context, { 'existing_server is':dest,'joining_server' : server })
   
    json_data = json.dumps(server)
    debug.comment(context, 'json_data created')
    s = 'http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/join"                        
    f = urllib2.urlopen(s,urllib.urlencode({'data':json_data}))
    debug.comment(context, 'urllib2 call to url: ' + s + " completed")
    assert f.info().gettype() == 'application/json'
    a = f.read()
    if a == "null":
        debug.leave(context, {'shit shit shit' : 'badness 100000'})
        raise Exception("error joining server " + str(dest.ip)+":"+str(dest.port) +" perhaps the other server is down?")
    debug.comment(context, 'f.read() is ' + str(a) )
    response = json.loads(a) # this is a dictionary containing pred and succ
    debug.comment(context, 'response is ' + str(response))
    # grab the pred and succ data, package them up
    pred = response['predecessor']
    succ = response['successor']
    debug.leave(context, {'pred':pred,'succ':succ})
    return {"successor":succ, "predecessor":pred}

def leave(dest, predecessor):
    """ Sends a notification to dest that his predecessor is leaving.
    @author: rk
    @rtype: True on success, None on failure
    """
    json_data = json.dumps(predecessor)
    try:
        f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/leave", urllib.urlencode({'data':json_data}))
        assert f.info().gettype() == 'application/json'
        response = json.loads(f.read())
    except Exception, e:
        response = json.loads({ 'exception' : str(e) })
    return response

def notify(dest, server):
    """ Sends a notify signal to dest on behalf of given server.
    @author: rk
    @rtype: True on success, None on failure
    @todo: add error recovery and ``None`` return case
    """
    json_data = json.dumps(server)
    f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/notify",
                        urllib.urlencode({'data':json_data}))
    assert f.info().gettype() == 'application/json'
    response = json.loads(f.read())
    return True

def update_successor(dest, server):
    """ Induces dest to update its successor to server.
    @author: rk
    @rtype: True on success, None on failure
    @todo: add error recovery and ``None`` return case
    """
    json_data = json.dumps(server)
    f = urllib2.urlopen('http://' + str(dest.ip) + ":" + str(dest.port) + "/chord/update_successor",
                        urllib.urlencode({'data':json_data}))
    assert f.info().gettype() == 'application/json'
    response = json.loads(f.read())
    return response
